package com.kirito.mvcframework.v2;

import com.kirito.mvcframework.Annotation.*;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * @ClassName DispatcherServlet
 * @Description: TODO
 * @Author kirito
 * @Date 2021/1/7
 * @Version V1.0
 **/
public class DispatcherServlet extends HttpServlet {
    //保存配置文件
    private Properties contextConfig = new Properties();
    private List<String> classNames = new ArrayList<String>();
    private Map<String,Object> ioc = new HashMap<String, Object>();
    private Map<String,Method> handlerMapping = new HashMap<String, Method>();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req,resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            resp.getWriter().write(Arrays.toString(e.getStackTrace()));
            e.printStackTrace();
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        //1 获取请求的url
        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replaceAll(contextPath,"").replaceAll("/+","/");
        //2 获取请求的参数
        Map<String,String[]> parames = req.getParameterMap();
        //3 从handlerMapping中 对应的方法
        if (!handlerMapping.containsKey(url)) {
            resp.getWriter().write("404 not found");
            return;
        }
        //获取方法的参数列表
        Method method = handlerMapping.get(url);

        //获取所有的参数类型
        Class<?>[] parameterTypes = method.getParameterTypes();
        Object[] parameValue = new Object[parameterTypes.length];
        int i = 0 ;
        for (Class parameterType : parameterTypes) {

            if (parameterType == HttpServletRequest.class){
                parameValue[i] = req ;
            }else if (parameterType == HttpServletResponse.class){
                parameValue[i] = resp ;
            }else if(parameterType == String.class){
                Annotation[][] pa = method.getParameterAnnotations();
                for (int j = 0; j < pa.length ; j ++){
                    for (Annotation a : pa[i]){
                        if (a instanceof RequestParam){
                            String paramName =((RequestParam)a).value();
                            if (parames.containsKey(paramName)) {
                                for (Map.Entry<String, String[]> entry : parames.entrySet()) {
                                    String value = Arrays.toString(entry.getValue())
                                            .replaceAll("\\[|\\]","")
                                            .replaceAll("\\s",",");
                                    parameValue[i] = value;

                                }
                            }
                        }
                    }
                }
            }
            i++;
        }
        String name = method.getDeclaringClass().getName();
        method.invoke(ioc.get(name),parameValue);
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        //1,加载配置文件
        doLoadConfig(config.getInitParameter("contextConfigLocation"));
        //2,扫描相关的类
        doScanner(this.contextConfig.getProperty("scanPackage"));
        //3,初始化扫描的类
        doInstance();
        //4,依赖注入
        doAutowired();
        //5,初始化handlerMapping
        doHandlerMapping();
    }

    private void doHandlerMapping() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(Controller.class)){continue;}
            String  baseUrl = "";
            //获取类上的注解 "/demo"
            if (!clazz.isAnnotationPresent(RequestMapping.class)){continue;}
            baseUrl = clazz.getAnnotation(RequestMapping.class).value();
            for (Method method : clazz.getMethods()) {
                //获取方法上的注解
                if (!method.isAnnotationPresent(RequestMapping.class)){continue;}
                String url  = method.getAnnotation(RequestMapping.class).value();
                url = (baseUrl + "/" + url).replaceAll("/+","/");
                handlerMapping.put(url,method);
                System.out.println("Mapped :" + url + "," + method);
            }

        }

    }

    private void doAutowired() {
        if (ioc.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field : fields) {

                try {
                    if (!field.isAnnotationPresent(Autowired.class)) { continue; }
                    String beanName = field.getAnnotation(Autowired.class).value();
                    if ("".equals(beanName)){
                        //获取接口的全类名
                        beanName = field.getType().getName();
                    }
                    // 在扫描的类名容器 匹配实现了该接口的
                    for (String className : classNames) {
                        Class<?> clazz = Class.forName(className);
                        if (clazz.isInterface()){continue;}
                        for (Class<?> anInterface : clazz.getInterfaces()) {
                           if ( anInterface.getName().equals(beanName) ){
                               beanName = className;
                               break;
                           }
                        }
                    }
                    field.setAccessible(true);
                    field.set(entry.getValue(),ioc.get(beanName));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("自动注入完毕");
    }

    private void doInstance() {
        if (classNames.isEmpty()){return;}
        for (String className: classNames) {
            try {
                Class<?> clazz = Class.forName(className);

                if (clazz.isAnnotationPresent(Controller.class)){
                    //@Controller
                    //这里偷个懒 不做标准化
                    ioc.put(clazz.getName(),clazz.newInstance());
                }else if(clazz.isAnnotationPresent(Service.class)){
                    //@Service
                    Service service = clazz.getAnnotation(Service.class);
                    String beaName = service.value();
                    if ("".equals(beaName)){ beaName = clazz.getName(); }
                    ioc.put(beaName,clazz.newInstance());
                }else{
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println("ioc 初始化完毕");
    }

    private void doScanner(String scanPackage) {
        //路径转换
        URL url = this.getClass().getClassLoader().getResource("/" + scanPackage.replaceAll("\\.", "/"));
        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()){
                doScanner(scanPackage+"."+file.getName());
            }else{
                if (!file.getName().endsWith(".class")){continue;}
                //保存全类名
                String beanName = (scanPackage + "."+file.getName().replace(".class",""));
                classNames.add(beanName);
            }
        }
    }

    private void doLoadConfig(String contextConfigLocation) {
        InputStream is = null;
        is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
        try {
            this.contextConfig.load(is);
            System.out.println("配置文件已加载");
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (is!= null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
